
const { ccclass, property } = cc._decorator;
//节点结构体
type vexData = {
    x: number,
    y: number,
    position: cc.Vec2,
    F: number,
    G: number,
    H: number,
    parentVex: any
}
/**
 * Astar Component
 * GameMgr:游戏管理者
 * 1.脚本绑定在角色身上
 * 2.GameMgr: 点击后,调用init更新地图信息
 * 3.GameMgr: 点击后,调用search,参数:(起点,终点),返回值:最短路径数组
 * 如果是tiledmap,起点坐标 =  Math.floor(局部坐标/  tile 元素的大小)
 */
@ccclass
export default class AStarCom extends cc.Component {

    private _startPos = cc.v2()
    private _endPos = cc.v2()
    private vexList: any
    private graph: any

    /**
     * 
     * @param graph 地图信息:二维数组
     */
    public init(graph) {
        this.graph = graph
        this.vexList = new Array(this.graph.length)
        for (let i = 0; i < this.vexList.length; ++i) {
            this.vexList[i] = new Array(this.graph[0].length)
        }
        this.createMap(this.vexList)
    }

    protected astar(start, end, graph, vexList) {
        let openList = [],
            closeList = []
        let direction = [
            [0, 1],
            [1, 0],
            [0, -1],
            [-1, 0]
        ] //顺时针
        openList.push(start)
        while (openList.length > 0) {
            //从openList中获取F最小的顶点,从open列表移除后加入close列表
            let minVex = this.getVexOfMinF(openList)
            // cc.log("`open表", openList, minVex)
            // cc.log("`minVex", minVex)
            if (minVex == end) {
                return true
            }

            this.removeVexFromArray(openList, minVex)
            closeList.push(minVex)
            // console.log(openList, closeList)

            //遍历周围可到达的，不在close列表中，且不是障碍物的顶点
            for (let i = 0; i < direction.length; ++i) {
                let nextX = minVex.x + direction[i][0]
                let nextY = minVex.y + direction[i][1]
                if (nextX >= 0 && nextX < graph.length && nextY >= 0 && nextY < graph[0].length && graph[nextX][nextY] == 0 && !this.getVexFromList(closeList, vexList[nextX][nextY])) {
                    if (this.getVexFromList(openList, vexList[nextX][nextY])) { //在openList中
                        //计算该探测顶点能否通过当前处理的最小顶点使得G值变小
                        if (minVex.G + 1 < vexList[nextX][nextY].G) {
                            vexList[nextX][nextY].parentVex = minVex
                            vexList[nextX][nextY].G = minVex.G + 1
                            vexList[nextX][nextY].F = vexList[nextX][nextY].H + vexList[nextX][nextY].G
                        }
                    } else { //不在openList中
                        vexList[nextX][nextY].parentVex = minVex
                        vexList[nextX][nextY].G = minVex.G + 1
                        vexList[nextX][nextY].H = this.estimatedH(vexList[nextX][nextY], end)
                        vexList[nextX][nextY].F = vexList[nextX][nextY].H + vexList[nextX][nextY].G
                        openList.push(vexList[nextX][nextY])
                    }
                }
            }
        }
        return false
    }
    /**
     * 
     * @param startpos 起点坐标
     * @param endpos 终点坐标
     */
    public search(startpos, endpos) {
        this._endPos = this.vexList[endpos.x][endpos.y]
        this._startPos = this.vexList[startpos.x][startpos.y]//某一个结构体

        return this.getPath(this._startPos, this._endPos, this.vexList)
    }
    protected createMap(vexList) {
        //将顶点保存在二维数组中，顶点坐标就为顶点在二维数组中的行列号
        //创建顶点,图为5*5，顶点坐标为0,0 - 4,4
        for (let i = 0; i < this.graph.length; ++i) {
            for (let j = 0; j < this.graph[0].length; ++j) {
                let road: vexData = {
                    x: 0,
                    y: 0,
                    position: cc.v2(),
                    F: 0,
                    G: 0,
                    H: 0,
                    parentVex: null
                }
                road.x = i;
                road.y = j;
                road.position = cc.v2(75 * i, 75 * j)
                vexList[i][j] = road
            }
        }
    }
    protected getVexOfMinF(openList) {
        // cc.log("`open表", openList)
        let minF = openList[0].F
        let minVex = openList[0]
        for (let i = 1; i < openList.length; ++i) {
            if (openList[i].F < minF) {
                minF = openList[i].F
                minVex = openList[i]
            }
        }
        return minVex
    }

    protected estimatedH(vex, end) {
        let disX = Math.abs(vex.x - end.x)
        let disY = Math.abs(vex.y - end.y)
        let H = disX + disY
        return H
    }
    protected getVexFromList(array, vex) {
        for (let i = 0; i < array.length; ++i) {
            if (array[i] == vex) {
                return array[i]
            }
        }
        return false
    }
    protected removeVexFromArray(array, vex) {
        for (let i = 0; i < array.length; ++i) {
            if (array[i] == vex) {
                array.splice(i, 1)
                break;
            }
        }
    }
    protected getPath(start, end, vexList) {
        let res = this.astar(start, end, this.graph, vexList)
        var path = []
        let vex = end
        while (vex.parentVex) {
            path.push(vex)
            vex = vex.parentVex
        }
        path.push(start)
        let paths = []
        while (path.length) {
            let vex = path.pop()
            // vex.node.opacity = 100
            // console.log(vex.x + ',' + vex.y + '->')
            let x = vex.x * 75 + 37.5
            let y = vex.y * 75 + 37.5
            paths.push([x, y])
        }
        return paths;
    }

    protected resetVex(vexList) {
        for (let i = 0; i < vexList.length; ++i) {
            for (let j = 0; j < vexList[i].length; ++j) {
                vexList[i][j].node.opacity = 255
            }
        }
    }
}
